#include "pmm.h"
#include "util/functions.h"
#include "riscv.h"
#include "config.h"
#include "util/string.h"
#include "memlayout.h"
#include "spike_interface/spike_utils.h"

// _end is defined in kernel/kernel.lds, it marks the ending (virtual) address of PKE kernel
extern char _end[];
// g_mem_size is defined in spike_interface/spike_memory.c, it indicates the size of our
// (emulated) spike machine.
extern uint64 g_mem_size;

static uint64 free_mem_start_addr;  //beginning address of free memory
static uint64 free_mem_end_addr;    //end address of free memory (not included)

typedef struct node {
  struct node *next;
} list_node;

// g_free_mem_list is the head of the list of free physical memory pages
static list_node g_free_mem_list39;//用来管理专门存放小页的物理地址
static list_node g_free_mem_list48;//用来管理专门存放大页的物理地址


//
// actually creates the freepage list. each page occupies 4KB (PGSIZE)
//
static void create_freepage_list(uint64 start, uint64 end) {
  g_free_mem_list39.next = 0;//空表头
  g_free_mem_list48.next = 0;
  for (uint64 p = ROUNDUP(start, PGSIZE48); p + PGSIZE48 < start+(end-start)*3/4; p += PGSIZE48)// 将3/4的物理空间分给大页
  	free_page( (void *)p ,1);
  for (uint64 p = ROUNDUP(start+(end-start)*3/4, PGSIZE); p + PGSIZE < end; p += PGSIZE)//剩下的空间分给小页
    free_page( (void *)p ,0);
}


//
// place a physical page at *pa to the free list of g_free_mem_list (to reclaim the page)
//
void free_page(void *pa,int mode) {//增加一个mode参数来标志 释放的页面是大页还是小页 0代表小页 1代表大页
  if(mode==1)
  {
  	if (((uint64)pa % PGSIZE48) != 0 || (uint64)pa < free_mem_start_addr || (uint64)pa >= free_mem_end_addr)//---------------------
	panic("free_page 0x%lx \n", pa);

     // insert a physical page to g_free_mem_list48
     list_node *n = (list_node *)pa;
     n->next = g_free_mem_list48.next;
     g_free_mem_list48.next = n;
  }else
  {
      if (((uint64)pa % PGSIZE) != 0 || (uint64)pa < free_mem_start_addr || (uint64)pa >= free_mem_end_addr)//---------------------
	panic("free_page 0x%lx \n", pa);

     // insert a physical page to g_free_mem_list39
     list_node *n = (list_node *)pa;
     n->next = g_free_mem_list39.next;
     g_free_mem_list39.next = n;
  }
}

//
// takes the first free page from g_free_mem_list, and returns (allocates) it.
// Allocates only ONE page!
//
void *alloc_page(int mode) {//新增一个mode参数 0表示分配一个小页 1表示分配一个大页
  if(mode==0)
  {
      list_node *n = g_free_mem_list39.next;
      if (n) g_free_mem_list39.next = n->next;

      return (void *)n;
  }else
  {
      list_node *n = g_free_mem_list48.next;
      if (n) g_free_mem_list48.next = n->next;

      return (void *)n;
  }
}

//
// pmm_init() establishes the list of free physical pages according to available
// physical memory space.
//
void pmm_init() {
  // start of kernel program segment
  uint64 g_kernel_start = KERN_BASE;
  uint64 g_kernel_end = (uint64)&_end;

  uint64 pke_kernel_size = g_kernel_end - g_kernel_start;
  sprint("PKE kernel start 0x%lx, PKE kernel end: 0x%lx, PKE kernel size: 0x%lx .\n",
    g_kernel_start, g_kernel_end, pke_kernel_size);

  // free memory starts from the end of PKE kernel and must be page-aligined
  free_mem_start_addr = ROUNDUP(g_kernel_end , PGSIZE);

  // recompute g_mem_size to limit the physical memory space that PKE kernel
  // needs to manage
  g_mem_size = MIN(PKE_MAX_ALLOWABLE_RAM, g_mem_size);
  if( g_mem_size < pke_kernel_size )
    panic( "Error when recomputing physical memory size (g_mem_size).\n" );

  free_mem_end_addr = g_mem_size + DRAM_BASE;
  sprint("free physical memory address: [0x%lx, 0x%lx] \n", free_mem_start_addr,
    free_mem_end_addr - 1);

  sprint("kernel memory manager is initializing ...\n");
  // create the list of free pages
  create_freepage_list(free_mem_start_addr, free_mem_end_addr);
}
